use mongodb::{bson::doc, options::ClientOptions, Client};
use serde::Deserialize;
use std::env;
use std::fs;
use std::process::{exit, Command};
use std::time::Duration;
use tokio::time::sleep;

#[derive(Debug, Deserialize)]
struct MongoConfig {
    mongo: MongoDetails,
}

#[derive(Debug, Deserialize)]
struct MongoDetails {
    location: String,
    config_file: String,
    test: MongoTest,
}

#[derive(Debug, Deserialize)]
struct MongoTest {
    uri: String,
}

#[tokio::main]
async fn main() {
    let mongo_config = load_config("config.toml").expect("加载配置失败");

    // 获取命令行参数
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        eprintln!("用法: mongodb_script [start|stop]");
        exit(1);
    }

    let command = &args[1];
    println!("命令: {:?}", &mongo_config.mongo);

    match command.as_str() {
        "start" => {
            start_mongodb(
                &mongo_config.mongo.location,
                &mongo_config.mongo.config_file,
            )
            .await
        }
        "stop" => {
            #[cfg(windows)]
            stop_mongodb(&mongo_config.mongo.test.uri).await;

            #[cfg(unix)]
            stop_mongodb(
                &mongo_config.mongo.location,
                &mongo_config.mongo.config_file,
            );
        }
        _ => {
            eprintln!("无效的命令: {}，请使用 'start' 或 'stop'", command);
            exit(1);
        }
    }
}

fn load_config(filename: &str) -> Result<MongoConfig, Box<dyn std::error::Error>> {
    let config_str = fs::read_to_string(filename)?;
    let mongo_config: MongoConfig = toml::de::from_str(&config_str)?;
    Ok(mongo_config)
}

#[cfg(windows)]
async fn start_mongodb(location: &str, config_file: &str) {
    println!("启动 MongoDB...");

    let child = Command::new(format!("{}\\bin\\mongod.exe", location))
        .arg("--config")
        .arg(config_file)
        .creation_flags(DETACHED_PROCESS)
        .spawn()
        .expect("启动 MongoDB 失败");

    println!("MongoDB 已在后台启动，进程 ID: {}", child.id());

    sleep(Duration::from_secs(3)).await;
    match check_mongodb_connection("mongodb://localhost:27017").await {
        Ok(()) => println!("MongoDB 连接成功"),
        Err(err) => eprintln!("MongoDB 连接失败: {}", err),
    }
}

#[cfg(windows)]
async fn stop_mongodb(uri: &str) {
    println!("通过客户端关闭 MongoDB...");

    let client_options = ClientOptions::parse(uri)
        .await
        .expect("解析 MongoDB URI 失败");
    let client = Client::with_options(client_options).expect("创建 MongoDB 客户端失败");

    let result = client
        .database("admin")
        .run_command(doc! { "shutdown": 1 }, None)
        .await;

    match result {
        Ok(_) => println!("MongoDB 关闭成功"),
        Err(err) => {
            eprintln!("关闭 MongoDB 失败: {}", err);
            exit(1);
        }
    }
}

#[cfg(unix)]
async fn start_mongodb(location: &str, config_file: &str) {
    let child = Command::new(format!("{}/bin/mongod", location))
        .arg("--config")
        .arg(config_file)
        .output();

    match child {
        Ok(output) => println!("MongoDB 启动信息: {:?}", output),
        Err(err) => {
            eprintln!("启动 MongoDB 失败: {}", err);
            return;
        }
    }

    sleep(Duration::from_secs(3)).await;
    match check_mongodb_connection("mongodb://localhost:27017").await {
        Ok(()) => println!("MongoDB 连接成功"),
        Err(err) => eprintln!("MongoDB 连接失败: {}", err),
    }
}

#[cfg(unix)]
fn stop_mongodb(location: &str, config_file: &str) {
    println!("关闭 MongoDB...");
    let child = Command::new(format!("{}/bin/mongod", location))
        .arg("--config")
        .arg(config_file)
        .arg("--shutdown")
        .output();

    match child {
        Ok(output) => println!("MongoDB 关闭信息: {:?}", output),
        Err(err) => eprintln!("关闭 MongoDB 失败: {}", err),
    }
}

async fn check_mongodb_connection(location: &str) -> Result<(), Box<dyn std::error::Error>> {
    sleep(Duration::from_secs(3)).await;

    let client_options = ClientOptions::parse(location).await?;
    let client = Client::with_options(client_options)?;

    client
        .database("admin")
        .run_command(doc! {"ping": 1}, None)
        .await?;

    Ok(())
}
